package ch.elca.lol.puissance4.player.ui;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;

import javax.swing.JPanel;

import ch.elca.lol.puissance4.board.Board;
import ch.elca.lol.puissance4.game.Token;

public class BoardPanel extends JPanel {
	private Board b;
	private Graphics2D gr;
	private Anim anim = null;
	private Token token;
	
	public void setToken(Token t) {
		this.token = t;
	}
	
	void setBoard(Board b) {
		this.b = b;
		this.repaint();
	}
	
	public void play(int col, Token t) {
		animate(col, t);
		setBoard(b.play(col, t));
	}
	
	private void animate(int col, Token t) {
		int squares = 0;
		
		for(int y = b.getHeight() - 1; y >= 0; y--) {
			if(b.unsafeGet(col, y) != null)
				break;
			squares++;
		}
		
		int dist = Size.marginOut + (squares - 1) * (Size.marginIn + Size.square);
		
		int steps = dist / 2;
		anim = new Anim(col, t);
		for(int i = 0; i < steps; i++) {
			anim.addDist(dist / steps);
			repaint();
			try {
				Thread.sleep(5);
			} catch(InterruptedException e) {
				e.printStackTrace();
			}
		}
		
		anim = null;
	}
	
	@Override
	public void paintComponent(Graphics g) {
		// Configure Graphics
		gr = (Graphics2D) g;
		gr.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		// gr.setComposite(AlphaComposite.SrcAtop);
		
		// In order. draw background -> tokens -> board -> text
		if(b != null) {
			drawBg();
			drawAnim();
			drawTokens();
			drawBoard();
			drawState();
		}
	}
	
	// Draw methods:
	
	private void drawAnim() {
		if(anim == null)
			return;
		
		int px = Size.marginOut + Size.marginIn / 2 + anim.getColumn() * (Size.square + Size.marginIn);
		int py = Size.marginIn / 2 + anim.getDist();
		
		switch(anim.getToken()) {
			case O:
				gr.setColor(Colors.O);
			break;
			case X:
				gr.setColor(Colors.X);
			break;
		}
		
		gr.fillOval(px, py, Size.square, Size.square);
	}
	
	private void drawBg() {
		gr.setColor(Colors.background);
		gr.fillRect(0, 0, getWidth(), getHeight());
	}
	
	private void drawState() {
		if(b.isGameOver()) {
			if(b.hasWon(token)) {
				text("You won :)");
			} else if(b.hasWon(token.next())) {
				text("You lost :(");
			} else {
				text("Stalemate");
			}
		}
		
		// TODO: Show "Your turn / Wait for the adversary to play (infotext)"
	}
	
	private void text(String text) {
		gr.setFont(Size.overText);
		Rectangle2D size = gr.getFontMetrics().getStringBounds(text, gr);
		int tx = (getWidth() - (int) size.getWidth()) / 2;
		int ty = (getHeight() - (int) size.getHeight()) / 2;
		
		int shadowDist = 4;
		// Shadow
		gr.setColor(Color.BLACK);
		gr.drawString(text, tx + shadowDist, ty + shadowDist);
		
		gr.setColor(Colors.overText);
		gr.drawString(text, tx, ty);
	}
	
	private void drawTokens() {
		Token t;
		for(int x = 0; x < b.getWidth(); x++) {
			for(int y = 0; y < b.getHeight(); y++) {
				t = b.unsafeGet(x, y);
				drawToken(t, x, y);
			}
		}
	}
	
	private void drawBoard() {
		// Create a clip area so as to not paint over tokens:
		Area a = new Area(new Rectangle2D.Double(0, 0, getWidth(), getHeight()));
		Shape tShape;
		for(int x = 0; x < b.getWidth(); x++) {
			for(int y = 0; y < b.getHeight(); y++) {
				tShape = getTokenClip(x, y);
				a.subtract(new Area(tShape));
			}
		}
		gr.setClip(a);
		
		// TODO: why doesn't anti-aliasing work ? 
		gr.setColor(Colors.board);
		gr.fillRect(0, 0, getWidth(), getHeight());
		
		gr.setClip(null);
	}
	
	private void drawToken(Token t, int x, int y) {
		if(t == null)
			return;
		
		int px = Size.marginOut + Size.marginIn / 2 + x * (Size.square + Size.marginIn);
		int py = Size.marginOut + Size.marginIn / 2 + (b.getHeight() - y - 1) * (Size.square + Size.marginIn);
		
		switch(t) {
			case O:
				gr.setColor(Colors.O);
			break;
			case X:
				gr.setColor(Colors.X);
			break;
		}
		
		gr.fillOval(px, py, Size.square, Size.square);
	}
	
	private Shape getTokenClip(int x, int y) {
		int px = Size.marginOut + Size.marginIn / 2 + x * (Size.square + Size.marginIn);
		int py = Size.marginOut + Size.marginIn / 2 + (b.getHeight() - y - 1) * (Size.square + Size.marginIn);
		return new Ellipse2D.Double(px, py, Size.square, Size.square);
	}
	
	@Override
	public Dimension getPreferredSize() {
		int w = 2 * Size.marginOut + tokenW() * (Size.square + Size.marginIn);
		int h = 2 * Size.marginOut + tokenH() * (Size.square + Size.marginIn);
		
		return new Dimension(w, h);
	}
	
	private int tokenW() {
		if(b == null)
			return 7;
		else
			return b.getWidth();
	}
	
	private int tokenH() {
		if(b == null)
			return 6;
		else
			return b.getHeight();
	}
	
	public int getColumn(int x) {
		int noMargin = x - Size.marginOut;
		int col = noMargin / (Size.square + Size.marginIn);
		return col;
	}
}

class Anim {
	private final int column;
	private final Token token;
	private int dist = 0;
	
	public Anim(int col, Token t) {
		this.column = col;
		this.token = t;
	}
	
	public void addDist(int incr) {
		dist += incr;
	}
	
	public Token getToken() {
		return token;
	}
	
	public int getDist() {
		return dist;
	}
	
	public int getColumn() {
		return column;
	}
}

class Colors {
	static final Color background = new Color(80, 80, 80);
	static final Color board = new Color(127, 127, 127);
	static final Color X = new Color(0, 0, 0);
	static final Color O = new Color(255, 255, 255);
	static final Color overText = new Color(255, 0, 0);
	static final Color infoText = new Color(0, 0, 0);
}

class Size {
	static final int square = 60;
	static final int marginOut = 20;
	static final int marginIn = 6;
	static final Font overText = new Font("Arial", Font.BOLD, 90);
	static final Font info = new Font("Artial", Font.PLAIN, 30);
}